use derivative::Derivative;
use serde::{Deserialize, Serialize};

use crate::features::dict::modelsgqo::Word;
use crate::features::explorer::{
  self,
  modelsdbo::{EditorDBO, FileDBO, FolderDBO},
};
use crate::features::gfilter::{
  self, DateTimeFilterIn, IntFilterIn, StringExactFilterIn, StringPatternFilterIn,
};
use crate::features::quizzes::modelsgqo::Quiz;
use async_graphql::{Context, ID};
use rbatis::rbatis::Rbatis;
use unicode_segmentation::UnicodeSegmentation;

#[derive(async_graphql::SimpleObject, Serialize, Deserialize, Clone, Debug)]
#[graphql(complex)]
#[derive(Derivative)]
#[derivative(Default)]
/// this is File
pub struct File {
  pub id: ID,
  pub snapshot_at: Option<String>,
  pub file_name: String,
  pub file_mode: Option<String>,
  pub file_owner: Option<String>,
  pub file_group: Option<String>,
  pub file_size: u32,
  pub title: Option<String>,
  pub description: Option<String>,
  pub content: Option<String>,
  /// local created timestamp, seconds since 1970-01-01
  pub created_at: i64,
  /// local updated timestamp, seconds since 1970-01-01
  pub updated_at: i64,
  pub status: Option<String>,
  pub folder_id: ID,
}

impl From<FileDBO> for File {
  fn from(item: FileDBO) -> Self {
    File {
      id: ID::from(item.id),
      snapshot_at: item.snapshot_at,
      file_name: item.file_name,
      file_mode: item.file_mode,
      file_owner: item.file_owner,
      file_group: item.file_group,
      file_size: item.file_size,
      title: item.title,
      description: item.description,
      content: item.content,

      created_at: item.created_at,
      updated_at: item.updated_at,
      status: item.status,
      folder_id: ID::from(item.folder_id),
      ..Default::default()
    }
  }
}

#[async_graphql::ComplexObject]
impl File {
  pub async fn from(&self) -> String {
    let mut from = String::new();
    from.push_str("<");
    from.push_str(&self.file_name);
    from.push_str(">");

    from
  }

  /// get English words from this English article
  async fn words(&self, ctx: &Context<'_>) -> Vec<Word> {
    let mut words: Vec<Word> = vec![];
    let content_ = self.content.to_owned();

    match content_ {
      Some(v) => {
        let mut w1 = v.unicode_words().collect::<Vec<&str>>();
        w1.sort();
        w1.dedup();

        w1.retain(|x| x.len() > 2);

        words = w1
          .into_iter()
          .map(|x| Word {
            id: ID::from(x.to_string()),
            word: x.to_string(),
            ..Default::default()
          })
          .collect();
      }
      _ => {}
    }
    words
  }
}

#[derive(async_graphql::InputObject, Serialize, Deserialize, Clone, Debug)]
pub struct NewFile {
  pub file_name: String,

  pub title: Option<String>,
  pub description: Option<String>,
  pub referer_url: Option<String>,
  /// parent Folder ID
  pub folder_id: ID,
}

#[derive(async_graphql::InputObject, Serialize, Deserialize, Clone, Debug)]
pub struct UpdateFile {
  pub file_name: Option<String>,
  pub title: Option<String>,
  pub description: Option<String>,
  pub content: Option<String>,
  pub referer_url: Option<String>,
  /// parent Folder ID
  pub folder_id: Option<ID>,
}

#[derive(async_graphql::SimpleObject, Serialize, Deserialize, Clone, Debug)]
#[graphql(complex)]
#[derive(Derivative)]
#[derivative(Default)]
/// this is Folder
pub struct Folder {
  pub id: ID,
  pub folder_name: String,
  pub snapshot_at: Option<String>,
  pub seq: Option<u32>,
  pub title: Option<String>,
  pub description: Option<String>,
  pub referer_url: Option<String>,
  pub ancestry: Option<String>,

  /// local created timestamp, seconds since 1970-01-01
  pub created_at: i64,
  /// local updated timestamp, seconds since 1970-01-01
  pub updated_at: i64,
  pub status: Option<String>,

  pub pid: Option<ID>,
}

impl From<FolderDBO> for Folder {
  fn from(item: FolderDBO) -> Self {
    Folder {
      id: ID::from(item.id),
      snapshot_at: item.snapshot_at,
      seq: item.seq,
      folder_name: item.folder_name,
      title: item.title,
      description: item.description,

      referer_url: item.referer_url,

      created_at: item.created_at,
      updated_at: item.updated_at,
      status: item.status,

      pid: match item.pid {
        Some(v) => Some(ID::from(v)),
        _ => None,
      },
      ..Default::default()
    }
  }
}
#[async_graphql::ComplexObject]
impl Folder {
  pub async fn from(&self) -> String {
    let mut from = String::new();
    from.push_str(&self.folder_name);
    from.push_str("<");
    from.push_str(&self.id);
    from.push_str(">");

    from
  }

  /// get foder members of the folder
  pub async fn folders(&self, ctx: &Context<'_>) -> Option<Vec<Folder>> {
    let rbatis_pool = ctx.data_unchecked::<Rbatis>();
    let id = &self.id.to_owned();
    let some_folders = explorer::services::get_sub_folders(rbatis_pool, &id.0).await;

    match some_folders {
      Some(v) => Some(v.into_iter().map(|x| x.into()).collect()),
      _ => None,
    }
  }

  /// get file members of the folder
  pub async fn files(&self, ctx: &Context<'_>) -> Option<Vec<File>> {
    let rbatis_pool = ctx.data_unchecked::<Rbatis>();
    let id = &self.id.to_owned();
    let some_files = explorer::services::get_folder_files(rbatis_pool, &id.0).await;

    match some_files {
      Some(v) => Some(v.into_iter().map(|x| x.into()).collect()),
      _ => None,
    }
  }

  /// get quiz members of the folder
  pub async fn quizzes(&self, ctx: &Context<'_>) -> Option<Vec<Quiz>> {
    let rbatis_pool = ctx.data_unchecked::<Rbatis>();
    let id = &self.id.to_owned();
    let some_files = explorer::services::get_folder_quizzes(rbatis_pool, &id.0).await;

    match some_files {
      Some(v) => Some(v.into_iter().map(|x| x.into()).collect()),
      _ => None,
    }
  }
}

#[derive(async_graphql::InputObject, Serialize, Deserialize, Clone, Debug)]
/// new folder request data
pub struct NewFolder {
  pub folder_name: String,
  pub title: Option<String>,
  pub description: Option<String>,
  pub referer_url: Option<String>,
  /// parent Folder ID
  pub pid: ID,
}

#[derive(async_graphql::InputObject, Serialize, Deserialize, Clone, Debug)]
/// update folder request data
pub struct UpdateFolder {
  pub folder_name: Option<String>,
  pub title: Option<String>,
  pub description: Option<String>,
  pub referer_url: Option<String>,
  /// parent Folder ID
  pub pid: Option<ID>,
}

#[derive(async_graphql::InputObject, Serialize, Deserialize, Clone, Debug)]
/// open editor request data
pub struct OpenEditor {
  pub id: ID,
  pub title: String,
}

#[derive(async_graphql::InputObject, Serialize, Deserialize, Clone, Debug)]
/// close editor request data
pub struct CloseEditor {
  pub id: ID,
}

#[derive(async_graphql::InputObject, Serialize, Deserialize, Clone, Debug)]
pub struct FileFilterIn {
  /// default condition fields logic relation, AND the provided conditions
  pub and: Option<Vec<FileFilterIn>>,

  /// or condition fields logic relation, OR the provided conditions
  pub or: Option<Vec<FileFilterIn>>,

  /// not condition fields logic relation, not the provided condition
  pub not: Box<Option<FileFilterIn>>,

  pub created_at: Option<DateTimeFilterIn>,
  pub updated_at: Option<DateTimeFilterIn>,
  pub status: Option<StringExactFilterIn>,
}

#[derive(async_graphql::InputObject, Serialize, Deserialize, Clone, Debug)]
pub struct FolderFilterIn {
  /// default condition fields logic relation, AND the provided conditions
  pub and: Option<Vec<FolderFilterIn>>,

  /// or condition fields logic relation, OR the provided conditions
  pub or: Option<Vec<FolderFilterIn>>,

  /// not condition fields logic relation, not the provided condition
  pub not: Box<Option<FolderFilterIn>>,

  pub created_at: Option<DateTimeFilterIn>,
  pub updated_at: Option<DateTimeFilterIn>,
  pub status: Option<StringExactFilterIn>,
}

#[derive(async_graphql::SimpleObject, Serialize, Deserialize, Clone, Debug)]
#[graphql(complex)]
#[derive(Derivative)]
#[derivative(Default)]
/// this is Editor
pub struct Editor {
  pub id: ID,
  pub snapshot_at: Option<String>,

  pub title: Option<String>,

  /// local created timestamp, seconds since 1970-01-01
  pub created_at: i64,
  /// local updated timestamp, seconds since 1970-01-01
  pub updated_at: i64,
  pub status: Option<String>,
}

impl From<EditorDBO> for Editor {
  fn from(item: EditorDBO) -> Self {
    Editor {
      id: ID::from(item.id),
      snapshot_at: item.snapshot_at,

      title: item.title,

      created_at: item.created_at,
      updated_at: item.updated_at,
      status: item.status,

      ..Default::default()
    }
  }
}
#[async_graphql::ComplexObject]
impl Editor {
  pub async fn from(&self) -> String {
    let mut from = String::new();
    from.push_str("Editor");
    from.push_str("<");
    from.push_str(&self.id);
    from.push_str(">");

    from
  }
}

#[derive(async_graphql::InputObject, Serialize, Deserialize, Clone, Debug)]
pub struct EditorFilterIn {
  /// default condition fields logic relation, AND the provided conditions
  pub and: Option<Vec<EditorFilterIn>>,

  /// or condition fields logic relation, OR the provided conditions
  pub or: Option<Vec<EditorFilterIn>>,

  /// not condition fields logic relation, not the provided condition
  pub not: Box<Option<EditorFilterIn>>,

  pub created_at: Option<DateTimeFilterIn>,
  pub updated_at: Option<DateTimeFilterIn>,
  pub status: Option<StringExactFilterIn>,
}
